package mx.com.crowdgine.util.math;

/**
 * A 4-dimensional vector.
 * @author Oriam De Gyves
 */
public class Vector4f {
	/**
	 *  A vector whose components are all zero.
	 */
	public static final Vector4f ZERO = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
	
	private float x = 0;
    private float y = 0;
    private float z = 0;
    private float w = 0;

    /** Creates a new vector initialized with zeroes.*/
    public Vector4f(){
        x = 0;
        y = 0;
        z = 0;
        w = 0;
    }

    /**
     * Creates a new vector initialized with the parameters values.
     * @param x The value to set the x component.
     * @param y The value to set the y component.
     * @param z The value to set the z component.
     * @param w The value to set the w component.
     */
    public Vector4f(float x, float y, float z, float w){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }

    /**
     * Creates a new vector initialized with an array.
     * @param array A size-4 array to be mapped with the x, y, z and w values.
     * @throws RuntimeException If the length of the array is different from 4.
     */
    public Vector4f(float[] array){
        if(array.length != 4)
            throw new RuntimeException("Vector4f constructor: Only 4 element arrays are accepted");

        x = array[0];
        y = array[1];
        z = array[2];
        w = array[3];
    }
    
    /**
     * Creates a new vector initialized with a quaternion.
     * @param quat Quaterion to be mapped with the x, y, z and w values.
     */
    public Vector4f(Quaternion quat){
    	x = quat.getX();
    	y = quat.getY();
    	z = quat.getZ();
    	w = quat.getW();
    }

    /**
     * Creates an array representation of the vector.
     * @return Returns a float array of size 4 with the
     * elements of the vector.
     */
    public float[] array(){
        return new float[]{x,y,z,w};
    }

    /**
     * Adds a vector to the current vector. Doesn't change the vector's values.
     * @param other The vector that will be added.
     * @return A new vector which contains the result of the addition.
     * @see #plus(Vector4f)
     */
    public Vector4f add(Vector4f other){
        return new Vector4f(x + other.getX(),
                            y + other.getY(),
                            z + other.getZ(),
                            w + other.getW());
    }

    /**
     * Adds a vector to the current vector. Changes the vector's values.
     * @param other The vector that will be added.
     * @see #add(Vector4f)
     */
    public void plus(Vector4f other){
        this.x += other.getX();
        this.y += other.getY();
        this.z += other.getZ();
        this.w += other.getW();
    }

    /**
     * Subtracts a vector to the current vector. Doesn't change the vector's values.
     * @param other The vector that will be subtracted.
     * @return A new vector which contains the result of the subtraction.
     * @see #minus(Vector4f)
     */
    public Vector4f sub(Vector4f other){
        return new Vector4f(x - other.getX(),
                            y - other.getY(),
                            z - other.getZ(),
                            w - other.getW());
    }

    /**
     * Subtracts a vector to the current vector. Changes the vector's values.
     * @param other The vector that will be subtracted.
     * @see #sub(Vector4f)
     */
    public void minus(Vector4f other){
        this.x -= other.getX();
        this.y -= other.getY();
        this.z -= other.getZ();
        this.w -= other.getW();
    }

    /**
     * Negates the vector's values. Doesn't change the vector's values.
     * @return A new vector which contains the negation of the current vector's values.
     * @see #negate()
     */
    public Vector4f neg(){
        return new Vector4f(-x, -y, -z, -w);
    }

    /**
     * Negates the vector's values. Changes the current vector's values.
     * @see #neg()
     */
    public void negate(){
        x *= -1;
        y *= -1;
        z *= -1;
        w *= -1;
    }

    /**
     * Multiplies the vector with a scalar value. Doesn't change the vector's values.
     * @param scalar Scalar value to be multiplied.
     * @return A new vector with the result of the multiplication.
     * @see #scale(float)
     */
    public Vector4f mul(float scalar){
		return new Vector4f(scalar*x, scalar*y, scalar*z, scalar*w);
	}
    
    /**
     * Multiplies the vector with a scalar value. Changes the vector's values.
     * @param scalar Scalar value to be multiplied.
     * @see #mul(float)
     */
    public void scale(float scalar){
    	x *= scalar;
    	y *= scalar;
    	z *= scalar;
    	w *= scalar;
    }

    /**
     * Performs a dot operation with another vector.
     * @param other The vector to perform the dot operation with.
     * @return A float value with result of the dot operation.
     */
    public float dot(Vector4f other){
    	return 	x * other.getX() +
				y * other.getY() +
				z * other.getZ() +
				w * other.getW();
	}

    /**
     * 
     * @param V
     * @param W
     * @return
     */
    public Vector4f cross(Vector4f V, Vector4f W){
    	float d1, d2, d3, d4, d5, d6;
    	d1 = V.getX() * W.getY() - V.getY() * W.getX();
    	d2 = V.getX() * W.getZ() - V.getZ() * W.getX();
    	d3 = V.getX() * W.getW() - V.getW() * W.getX();
    	d4 = V.getY() * W.getZ() - V.getZ() * W.getY();
    	d5 = V.getY() * W.getW() - V.getW() * W.getY();
    	d6 = V.getZ() * W.getW() - V.getW() * W.getZ();
    	
    	float r1, r2, r3, r4;
    	r1 =  (y * d6) - (z * d5) + (w * d4);
    	r2 = -(x * d6) + (z * d3) - (w * d2);
    	r3 =  (x * d5) - (y * d3) + (w * d1);
    	r4 = -(x * d4) + (y * d2) - (z * d1);
    	
    	return new Vector4f(r1, r2, r3, r4);
    }
    
    /**
     * Calculates the length of the vector.
     * @return A float value with the length of the vector.
     */
    public float length(){
    	float len = lengthSqr();
    	if(len == 0.0f)
    		return 0.0f;
    	else
    		return (float)Math.sqrt(len);
    }
    
    /**
     * 
     * @return
     */
    public float lengthSqr(){
    	return this.dot(this);
    }

    /**
     * Normalizes the vector. Doesn't change the vector's values.
     * @return A new vector with the normalized values.
     * @see #normalize()
     */
    public Vector4f norm(){
    	float len = this.length();
    	if(len != 0)
    		return this.mul(1.0f/length());
    	else
    		return new Vector4f();
	}
    
    /**
     * Normalizes the vector. Changes the vector's values.
     * @see #norm()
     */
    public void normalize(){
    	float len = this.length();
    	if(len != 0){
    		len = 1.0f/len;
    		x *= len;
    		y *= len;
    		z *= len;
    		w *= len;
    	}
    }
    
    /**
     * Calculates the distance between the vector and another one.
     * @param other The vector to be used to calculate the distance.
     * @return A float with the total distance between the vectors.
     */
    public float distance(Vector4f other){
    	float dist = this.distanceSqr(other);
    	if(dist == 0.0f)
    		return 0.0f;
    	else
    		return (float)Math.sqrt(dist);
    }
    
    /**
     * 
     * @param other
     * @return
     */
    public float distanceSqr(Vector4f other){
    	float dx, dy, dz, dw;
    	dx = x - other.getX();
    	dy = y - other.getY();
    	dz = z - other.getZ();
    	dw = w - other.getW();
    	return (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw);
    }

    /**
    *
    * @return The x component of the vector.
    */
    public float getX(){
        return x;
    }

    /**
    *
    * @return The y component of the vector.
    */
    public float getY(){
        return y;
    }

    /**
    *
    * @return The z component of the vector.
    */
    public float getZ(){
        return z;
    }
    
    /**
    *
    * @return The w component of the vector.
    */
    public float getW(){
    	return w;
    }
    
    /**
     * Creates a string representation of the vector.
     * @return A string with the format: "(x,y,z,w)".
     */
    @Override
    public String toString(){
        return "(" + x + ", " + y + ", " + z + ", " + w + ")";
    }
    
    /**
     * Checks if the vector is equal to another one.<br /><br />
     * A vector is considered equal to another if the 
     * components in one have the same values as the components in the other one.
     * @param obj The vector to check equality with.
     * @return True if the vectors are equal, false otherwise.
     */
    @Override
    public boolean equals(Object obj){
    	if( obj instanceof Vector4f ){
    		Vector4f other = (Vector4f)obj;
    		
			return x == other.getX() &&
			       y == other.getY() &&
			       z == other.getZ() &&
			       w == other.getW();
		}else{
			return false;
		}
    }
}
